module Othello where

import Maybe
import Tablero
import Char
import List

data Juego = J Color Tablero deriving Eq

data Jugada = M Posicion | Paso
  deriving (Show, Eq)

data Arbol a = Nodo a [Arbol a] deriving (Show, Eq)
type ArbolJugadas = Arbol ([Jugada], Juego)

type Valuacion = Juego -> Double

instance Show Juego where
  show (J turno tablero) = "\n--Juega el " ++ show turno ++ "--\n" ++ show tablero

arbolDeJugadas :: Juego -> ArbolJugadas
arbolDeJugadas j = Nodo ([], j) $ zipWith agmov movs hijos
  where agmov m (Nodo (ms, r) hs) = Nodo ((m:ms), r) (map (agmov m) hs)
        movsJuegos = jugadasPosibles j
        movs = map fst movsJuegos
        hijos = map (arbolDeJugadas . snd) movsJuegos

-- Auxiliar

altura :: Arbol a -> Int
altura arbol = foldArbol (\elemento alturaSubarboles -> 1 + alturaSubarboles) (maximum. (0:)) arbol


-- Ejercicio 7

casillaVacia :: Posicion -> Tablero -> Bool
casillaVacia p t = isNothing (contenido p t)
        
esJugadaValida :: Jugada -> Juego -> Bool
esJugadaValida Paso  _       = True
esJugadaValida (M p) (J c t) = (enTablero p) && (casillaVacia p t) && seRodeanFichasDelAdversario
    where seRodeanFichasDelAdversario = not (null (posicionesAInvertir p nuevoTablero))
          nuevoTablero = poner p c t
               
efectuarJugada :: Jugada -> Juego -> Tablero
efectuarJugada Paso  (J c t) = t
efectuarJugada (M p) (J c t) = invertirTodas (posicionesAInvertir p nuevoTablero) nuevoTablero
    where nuevoTablero = poner p c t 

jugar :: Jugada -> Juego -> Maybe Juego
jugar j (J c t) | esJugadaValida j (J c t) = Just (J proximoJugador (efectuarJugada j (J c t)))
                | otherwise = Nothing
                where proximoJugador = adversario c
   
-- Ejercicio 8

jugadas :: [Jugada]
jugadas = map (M) posiciones


jugadasPosibles :: Juego -> [(Jugada, Juego)]
jugadasPosibles j | any ((flip esJugadaValida) j) jugadas = [(M p, fromJust (jugar (M p) j)) | p <- posiciones, esJugadaValida (M p) j]
                  | otherwise = [(Paso, fromJust (jugar Paso j))]


-- Ejercicio 9
-- Fold sobre Arbol que utiliza dos funciones para realizar la recursión:
-- La primera recibe el elemento del nodo actual y el resultado de combinar todos los resultados recursivos de los hijos
-- La segunda función combina todos los resultados parciales de los hijos en uno que se pasa como parámetro a la anteriora
foldArbol :: (a -> b -> c) -> ([c] -> b) -> Arbol a -> c
foldArbol f g (Nodo elemento children) = f elemento (g (map (foldArbol f g) children))


-- Ejercicio 10
-- Implementación de 'podar' utilizando foldArbol, definida como la
-- aplicación parcial de flip de podar'.
-- Se puede pensar que podar' devuelve una función (Int -> Arbol a), donde el
-- parámetro numérico se obtiene al momento de llamar a la función podar.
-- Al recibir este número se evalúa la función que se le pasa a foldArbol,
-- que va disminuyendo el número de nivel hasta llegar a 1, donde se dejan
-- de agregar los hijos al árbol resultante.
podar :: Int -> Arbol a -> Arbol a
podar = flip podar'
    where
            podar' :: Arbol a -> Int -> Arbol a
            podar' = foldArbol (\elemento rec n -> if n == 1 then (Nodo elemento [])
                                                   else (Nodo elemento $ map ((flip ($)) (n-1)) rec)) id


-- Ejercicio 11
-- Retorna la mejor jugada posible según el algoritmo de minimax.
-- Pregunta si estamos en una hoja, que indica que se llamó con profundidad 1.
-- En este caso minimax no nos dice nada, por lo cuál retornamos cualquier jugada.
mejorJugada :: Valuacion -> ArbolJugadas -> Jugada
mejorJugada val arbol   | altura arbol > 1 =  head (snd (minimax val arbol))
                        | otherwise = cualquierJugada arbol
    where
            -- para el estado del juego de la raíz del árbol, retorna la primer jugada posible.
            cualquierJugada :: ArbolJugadas -> Jugada
            cualquierJugada (Nodo (jugadas, juego) hijos) = fst ((jugadasPosibles juego)!!0)

-- Devuelve la mejor valuación obtenida por el algoritmo minimax, acompañada de la secuencia
-- de jugadas que conducen hasta el estado de juego que tiene dicho valor.
minimax :: Valuacion -> ArbolJugadas -> (Double, [Jugada])
minimax valuacion = foldArbol evaluoNivel minimizoAdversario
    where
            -- si el nodo actual es una hoja o representa un estado terminal, devuelvo el puntaje del tablero
            -- en caso contrario, utilizo el segundo parámetro (que contiene el mínimo puntaje del contrario
            -- sobre todas las jugadas posibles) y le invierto el valor, retornando la lista de jugadas que
            -- llevan a ese valor.
            evaluoNivel :: ([Jugada], Juego) -> Maybe (Double, [Jugada]) -> (Double, [Jugada])
            evaluoNivel (jugadas, juego) cosa   | isNothing cosa = (valuacion juego, jugadas)
                                                | abs (valuacion juego) == 1 = (valuacion juego, jugadas)
                                                | otherwise = (negate (fst (fromJust cosa)), snd (fromJust cosa))
            -- si la lista de hijos es vacía retorno Nothing para indicar que el nodo que me llamó se
            -- trataba de una hoja. en caso contrario retorno el resultado que minimiza el puntaje del
            -- oponente.
            minimizoAdversario :: [(Double, [Jugada])] -> Maybe (Double, [Jugada])
            minimizoAdversario l = if null l then Nothing else Just (foldr1 minFirst l)
            minFirst (a,b) (a', b') = if a < a' then (a,b) else (a',b')


-- Ejercicio 12
esPaso :: Jugada -> Bool
esPaso j = j == Paso

proximaJugada :: Juego -> Jugada
proximaJugada j = fst ( head ( jugadasPosibles j ) )

proximaJugadaEsPaso :: Juego -> Bool
proximaJugadaEsPaso j = esPaso (proximaJugada j)

termino :: Juego -> Bool
termino j = proximaJugadaEsPaso j && proximaJugadaEsPaso ( fromJust (jugar Paso j) )

jugador :: Juego -> Color
jugador (J c t) = c

casillerosOcupados :: Color -> Juego -> [Maybe Color]
casillerosOcupados j (J c t) = filter (== Just j) (colores t)

cantCasillerosOcupados :: Color -> Juego -> Int
cantCasillerosOcupados c j = length (casillerosOcupados c j)

ganador :: Juego -> Maybe Color
ganador j | termino j = if (cantFichasJugador > cantFichasAdversario) then Just (jugador j) else Just (adversario(jugador j))
          | otherwise = Nothing
    where cantFichasJugador = cantCasillerosOcupados (jugador j)  j
          cantFichasAdversario = cantCasillerosOcupados (adversario (jugador j))  j
          
-- Ejercicio 13
valuacionOthello :: Valuacion
valuacionOthello = \j -> if (termino j) then
                            if (ganador j == Just(jugador j)) then 1 else -1
                         else
                            2 * (fromIntegral(cantCasillerosOcupados (jugador j) j) / fromIntegral(length posiciones)) - 1
